package uk.ac.sanger.das.dasregistry.springrest.sources;

import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.log4j.Logger;
import org.biodas.jdas.client.SourcesClient;
import org.biodas.jdas.dassources.Capabilities;
import org.biodas.jdas.dassources.utils.DasSpec;
import org.biodas.jdas.dassources.utils.PropInterogator;
import org.biodas.jdas.dassources.utils.RegistrySourceAdapter;
import org.biodas.jdas.exceptions.DASClientException;
import org.biodas.jdas.exceptions.ValidationException;
import org.biodas.jdas.schema.servers.SERVER;
import org.biodas.jdas.schema.servers.SERVERS;
import org.biodas.jdas.schema.sources.CAPABILITY;
import org.biodas.jdas.schema.sources.PROP;
import org.biodas.jdas.schema.sources.SOURCE;
import org.biodas.jdas.schema.sources.SOURCES;
import org.biodas.jdas.validation.RegistrySourceValidator;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.CommonsClientHttpRequestFactory;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import com.mysql.jdbc.util.LRUCache;



public class Mirror {
	private String registryRootUrl;
	private RestTemplate temp = new RestTemplate();
	private RestTemplate tempAuth;//sends user pass/authentication in headers- only used for post,put deletes not for gets
	private Logger logger = Logger.getLogger(Mirror.class);
	String serverUrl;
	DasSpec defaultVersion=null;

	RegistrySourceValidator validator=new RegistrySourceValidator();
	private Credentials credentials;
	
	public static void main(String[] args) {
		String regRootUrl = "http://localhost:8080/regrest/";
		Mirror mirror = new Mirror(regRootUrl);
		mirror.mirror();
		

	}

	public Mirror(String registryRootUrl) {
		this.registryRootUrl = registryRootUrl;

		HttpClientParams params=new HttpClientParams();
		params.setAuthenticationPreemptive(true);
		params.setConnectionManagerClass(org.apache.commons.httpclient.MultiThreadedHttpConnectionManager.class);
		
		HttpClient httpClient=new HttpClient(params);
		CommonsClientHttpRequestFactory clientRequestFactory=new CommonsClientHttpRequestFactory(httpClient);
		tempAuth= new RestTemplate(clientRequestFactory);
		credentials = new UsernamePasswordCredentials("dasregistry@sanger.ac.uk", "blah");
		CommonsClientHttpRequestFactory factory = (CommonsClientHttpRequestFactory) tempAuth.getRequestFactory();
		HttpClient client = factory.getHttpClient();
		client.getState().setCredentials(AuthScope.ANY, credentials);
	}

	/**
	 * list of sources documents/servers we want to register or update
	 * 
	 * @return
	 */
	public List<SERVER> getServers() {
		ArrayList<SERVER> mirrors = new ArrayList<SERVER>();// list of sources
															// documents we want
															// to register or
															// update
		String url = registryRootUrl + "das/servers";
		SERVERS servers = temp.getForObject(url, SERVERS.class);
		// loop through the servers and get only the ones that are sources
		// controlled

		for (SERVER server : servers.getSERVER()) {
			// System.out.println("server "+server.getUri()+server.getServerControlled());
			if (server.getServerControlled() != null) {
				if (server.getServerControlled().equals("yes")) {
					logger.debug("adding server " + server.getUri());
					mirrors.add(server);
				}
			}
		}
		return mirrors;
	}

	public void mirror() {
		// get all sources already registered
		SOURCES registeredSources = getAlreadyRegisteredSources();
		List<SERVER> servers = this.getServers();
		for (SERVER server : servers) {
			mirrorServer(registeredSources, server);
		}

		//
	}

	public void mirrorServer(SOURCES registeredSources, SERVER server) {
		serverUrl = server.getUri();
		logger.debug("server for mirroring: " + serverUrl);
		List<SOURCE> sources = getRegisterableSources(server);
		if(sources==null || sources.size()==0)return;
		System.out.println("sources size=" + sources.size());
		
		sources = removeBamFileFormatSources(sources);
		System.out
				.println("registerable sources size in test after bam removal="
						+ sources.size());
		assertTrue(sources.size() > 0);
		List<SOURCE> sourcesForUpdate = getSourcesForUpdating(sources,
				registeredSources);
		int updatessize = sourcesForUpdate.size();
		System.out.println("update size=" + updatessize);
		putSources(sourcesForUpdate);

		List<SOURCE> newSources =newSourcesForRegistering(sources,
				sourcesForUpdate, registeredSources);

		int newSourcesSize = newSources.size();
		System.out.println("new sources size="+newSourcesSize);
		postSources(newSources);

		List<SOURCE> sourcesForDeletion = getSourcesForDeletion(sources,
				registeredSources);

		System.out.println("sourcesForDeletion size="
				+ sourcesForDeletion.size());

		deleteSources(sourcesForDeletion);

	}

	/**
	 * Get the sources we wont to post ie not in the registry already
	 * @param sources sources without a do not register me tag
	 * @param puts sources that we have already established exist already in the registry and so we are updating them in another method
	 * @param registeredSources 
	 * @return
	 */
	public List<SOURCE> newSourcesForRegistering(List<SOURCE> sources,List<SOURCE> puts, SOURCES registeredSources) {
		ArrayList<SOURCE> posts = new ArrayList<SOURCE>();// we want to post
		// these source
		for(SOURCE source:sources){

			try {
				if("".equals(validator.getRegistryId(source, puts)))
				{
					//if source is not a DS_ id and hasn't got a http: at the front we want to add the full url
					String uri=source.getUri();
					logger.debug("source not found in puts so adding to posts"+uri);
					
					if(uri.contains("DS_")||uri.contains("http:")){
						//do nothing
					}else{
						//we need to give the full url not just the data source name
						RegistrySourceAdapter adapter=new RegistrySourceAdapter(source);
						//serverUrl=adapter.getOriginalDataSourceUri();
						//uri=serverUrl+"/"+uri;
						//source.setUri(uri);
						String original=adapter.getOriginalDataSourceUri();
						source.setUri(original);
					}
					System.out.println("source in newSourcesFor registering="+source.getUri()+" desc="+source.getDescription());
					posts.add(source);
				}
			} catch (ValidationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				continue;
			}
		}
		return posts;
	}

	public List<SOURCE> getSourcesForUpdating(List<SOURCE> sources,
			SOURCES registeredAlready) {
		ArrayList<SOURCE> puts = new ArrayList<SOURCE>();// we want to
		// update these
		for (SOURCE source : sources) {
			
			String error = null;
			try {
				error = validator.getRegistryId(source, registeredAlready.getSOURCE());
			} catch (ValidationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			if(!"".equals(error)){
				logger.warn("source exists, regUri="+error);
				String regUri=error;
				source.setUri(regUri);
				puts.add(source);
			}
		}
		return puts;
	}

	/**
	 * get a list of sources that do not have a flag to say don't register ie.
	 * registerable sources for this server
	 * 
	 * @param server
	 * @param registeredSources
	 * @return
	 */
	public List<SOURCE> getRegisterableSources(SERVER server) {
		
		this.serverUrl=server.getUri();
		List<SOURCE> registerable = new ArrayList<SOURCE>();
		logger.info("getting registerable sources for server with uri"+server.getUri());
		String url=server.getUri();
		char lastChar = server.getUri().charAt(server.getUri().length() - 1);
		if (lastChar != '/')
			url += "/";
		SOURCES sources = null;
		SourcesClient sourcesClient=new SourcesClient();
		try {
		 sources = sourcesClient.fetchData(url + "sources");
                 //get the das version from the header of the document if no 1.6 in header then defaults to 1.53E
		 defaultVersion = sourcesClient.getDasVersion();
                 
		} catch (ValidationException e2) {
			// TODO Auto-generated catch block
			e2.printStackTrace();
		} catch (DASClientException e2) {
			// TODO Auto-generated catch block
			e2.printStackTrace();
		}

		
//		try {
//			sources = temp.getForObject(url + "sources",
//					SOURCES.class);
//			
//		} catch (RestClientException e1) {
//			// TODO Auto-generated catch block
//			e1.printStackTrace();
//			return null;
//		}
		for (SOURCE source : sources.getSOURCE()) {
			
			String sourceUri = source.getVERSION().get(0).getUri();
			RegistrySourceAdapter ra=new RegistrySourceAdapter(source);
			try {
				sourceUri = ra.getOriginalDataSourceUri();
			} catch (ValidationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			logger.warn("adding source to registerable sources with uri "+sourceUri);
			List<PROP> props = source.getVERSION().get(0).getPROP();
			// test there is a flag to say dont register what is this flag going
			// to be?
			if (PropInterogator.getPropByName("Register", props).equals("")) {
				// No register flag not found so add to list
				// System.out.println("source is in registry so should do a put");
				//test if the source itself has a spec assigned in the properties if not assign it the default spec that has 
				//been retrieved from the sources document headers returned
				String spec=PropInterogator.getPropByName("spec", props);
				
				if(spec.equals("")){
					PROP specProp=new PROP();
					specProp.setName("spec");
					specProp.setValue(defaultVersion.toString());
					props.add(specProp);
				}else{
					DasSpec dasSpec=DasSpec.convertToRegistrySpecification(spec);
					PROP specProp=PropInterogator.getProp("spec", props);
					specProp.setValue(dasSpec.toString());
					logger.warn("set spec prop="+dasSpec.toString());
				}
                                //if has sourcesDoc prop use it, if not create one so the registry knows where the sourcesDoc is and what server_id to use
                                String sourcesDoc="";
                                if(ra.getSourcesDoc().equals("")){
                                    PROP sourcesDocProp=new PROP();
                                    sourcesDocProp.setName("sourcesDoc");
                                    sourcesDocProp.setValue(server.getUri());
                                    source.getVERSION().get(0).getPROP().add(sourcesDocProp);
                                }
				
				registerable.add(source);
			}

		}
		return registerable;
	}

	public SOURCES getAlreadyRegisteredSources() {
		//remember we need active = any so we get all sources active or inactive
		SOURCES sources=temp.getForObject(registryRootUrl + "das/sources/all", SOURCES.class);
		List<SOURCE> noBam = this.removeBamFileFormatSources(sources.getSOURCE());
		SOURCES regS=new SOURCES();
		regS.getSOURCE().addAll(noBam);
		return regS;
	}

	public  void putSources(List<SOURCE> puts) {
		for (SOURCE source : puts) {
			postOrPutSource(source, HttpMethod.PUT);
		}

	}

	public void postSources(List<SOURCE> posts) {
		for (SOURCE source : posts) {
			logger.debug("mirror post source with uri="+source.getUri());
			postOrPutSource(source, HttpMethod.POST);
		}
	}

	public  void postOrPutSource(SOURCE source, HttpMethod method) {
		//put a pause in here to stop the registry from not responding to request due to request overload!
		try {
			Thread.sleep(50);
			} catch(InterruptedException e) {
			} 
		SOURCES createSources = new SOURCES();
//		if (!source.getUri().contains("http://")) {
//			source.setUri(serverUrl + source.getUri());
//		}
//		RegistrySourceAdapter regA=new RegistrySourceAdapter(source);
//		try {
//			source.setUri(regA.getOriginalDataSourceUri());
//		} catch (ValidationException e1) {
//			// TODO Auto-generated catch block
//			e1.printStackTrace();
//		}
		createSources.getSOURCE().add(source);

		HttpHeaders headers = new HttpHeaders();
		headers.setContentType(MediaType.APPLICATION_XML);
		//logger.debug(method.name()+source.getUri());
		HttpEntity<String> entity = new HttpEntity(createSources, headers);
		

		ResponseEntity<SOURCES> responseBody = null;
		try {
			// this only works if the source has been deleted before hand by
			// the sql query e.g. delete from registry where
			// nickname="dummy title";
					
						responseBody = tempAuth.exchange(registryRootUrl
								+ "das/sources/", method, entity,
								SOURCES.class);
					
				
			
		} catch (HttpClientErrorException e) {

			System.out.println("error=" + e.getStatusCode());
			System.out.println("error body=" + e.getResponseBodyAsString());
			// e.printStackTrace();
		}
	}

	/**
	 * check all the sources with the original uri in the registry and if they are not now in the list
	 * of sources in the sources document add them to the list for deletion
	 * @param sources from the server sources document
	 * @param registeredSources are all the sources in the registry
	 * @return
	 */
	public List<SOURCE> getSourcesForDeletion(List<SOURCE> sources,
			SOURCES registeredSources) {
		ArrayList<SOURCE> sourcesForDeletion=new ArrayList<SOURCE>();
		String serverUri = null;
		RegistrySourceValidator sourceValidator=new RegistrySourceValidator();
		try {
			serverUri = new RegistrySourceAdapter(sources.get(0)).getServerUri();
		} catch (ValidationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//if registered has the correct root url but is not in the sources then it's for deletion
		for(SOURCE registered:registeredSources.getSOURCE()){
			//System.out.println(registered.getUri());
			RegistrySourceAdapter ra=new RegistrySourceAdapter(registered);
			String registeredSourceServerUri = null;
			try {
				registeredSourceServerUri = ra.getServerUri();
			} catch (ValidationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				continue;
			}
			//logger.debug("registry source uri="+sourceUri);
			if(registeredSourceServerUri.equals(serverUri)){
				logger.debug("root uris match:"+registeredSourceServerUri);
				//sourcesForDeletion.add(arg0)
				String exists = null;
				try {
					exists = sourceValidator.getRegistryId(registered, sources);
				} catch (ValidationException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				if(!exists.equals("")){
					System.out.println("adding source for deletion");
					sourcesForDeletion.add(registered);
				}
			}
		}
		return sourcesForDeletion;
	}
	
	public void deleteSources(List<SOURCE> sourcesForDeletion){
		for(SOURCE deleteS:sourcesForDeletion)
		this.delete(deleteS.getUri());
	}
	
	public void delete(String dsUnderScoreId){
		
		try {
			tempAuth.delete(this.registryRootUrl + "das/sources/" + dsUnderScoreId);
		} catch (RestClientException e1) {
			fail("client exception when trying to delete source");
			e1.printStackTrace();
		}
	}
	
	public List<SOURCE> removeBamFileFormatSources(List <SOURCE> registeredSources){
		List<SOURCE> filteredSources=new ArrayList<SOURCE>();
		for(SOURCE source:registeredSources){
			RegistrySourceAdapter ra=new RegistrySourceAdapter(source);
			if(!ra.hasBigFileFormat()){
				filteredSources.add(source);
			}
		}
		return filteredSources;
	}
}

